#include <boost/tokenizer.hpp>
#include <boost/algorithm/string.hpp>
#include <fstream>
#include <iostream>
#include <sstream>
#include "actor.hpp"
#include "instr.hpp"
#include "arith.hpp"
#include "argument_separator.hpp"
#include "assert_test.hpp"

using namespace std;
using namespace boost;

#define FUNCTION_USAGE_ERROR    "usage: -function, FUNC_NAME, NUM_ARGS"
#define ASSERT_USAGE_ERROR      "usage: -assert, ASSERTION_TYPE, [args]"
#define ACC_EQUALS_USAGE_ERROR  "usage: -assert, acc, VALUE"
#define ALLOCFIELDS_USAGE_ERROR "usage: -allocfields, NUM_FIELDS"
#define INIT_USAGE_ERROR        "usage: -init, FUNC_NAME"

namespace BMD
{
  typedef argument_separator arg_sep;
  typedef tokenizer<arg_sep> arg_sep_tokenizer;
  typedef tokenizer<arg_sep>::iterator tok_itr;

  /** load the BMD instructions from a file */
  void Actor::load_from_file(string filename)
  {
    ifstream in(filename.c_str());
    
    if (!in.is_open())
      throw file_parse_error("unable to open file: " + filename);
    
    clear();
 
    // first pass: reading in labels
    string line;
    int op_num = 0;
    while (getline(in, line))
    {
      trim(line);
      
      // skip empty lines
      if (line.empty())
        continue;
      
      // check labels
      if (is_label(line))
      {
        string label_name = trim_right_copy_if(line, is_any_of(": "));
        label_map.insert(make_pair(label_name, op_num));
      }
      // ignore comments
      else if (is_comment(line))
        continue;
      // compier signals
      else if (is_execution_signal(line))
      {
        try
        {
          parse_execution_signal(line, op_num);
        }
        catch (...)
        {
          clear();
          throw;
        }
      }
      else
        ++op_num;
    }
    in.close();
    
    ifstream in2(filename.c_str());
      
    if (!in2.is_open())
    {
      clear();
      throw file_parse_error("unable to open file: " + filename);
    }

    int line_num = 0;
    while (getline(in2, line) && ++line_num)
    {
      trim(line);
      if (line.empty() || is_label(line) || 
          is_comment(line) || is_execution_signal(line))
        continue;
   
      try
      {
        // parse the instruction
        InstructionRef new_instr(parse_instruction(line));
        
        // append the file information
        new_instr->set_line_number(line_num);
        
        // add the instruction to the list
        add_instruction(new_instr);
      }
      catch (instruction_parse_error e)
      {
        // reject all instructions, since having an incomplete load is useless
        clear();

        // attach the line number to any error with insturction parsing and
        // rethrow
        ostringstream error_msg;
        error_msg << filename << ":" << line_num << " - " << e.what();
        throw instruction_parse_error(error_msg.str());
      }
    }
    
    in2.close();
    
    // once we're done loading, try to run the function, and reset the vm afterwards
    if (init_func != "")
    {
      try
      {
        vector<Value> args;
        call_function(init_func, args);
        vm->reset();
      }
      catch (runtime_error e)
      {
        throw runtime_error(string("Error running initialization function: ") + e.what());
      }
    }
  }

  /** returns true iff the line is a comment. 
      Comments start with //. */
  bool Actor::is_comment(string line)
  {
    return istarts_with(line, "//");
  }
  
  /** returns true iff the line is a label
      Labels end with :. */
  bool Actor::is_label(string line)
  {
    return iends_with(line, ":");
  }

  /** execution signals start with a hyphen (-)
   * @return true iff the line is a special compiler.
   */      
  bool Actor::is_execution_signal(string line)
  {
    return istarts_with(line, "-");
  }

  void Actor::parse_execution_signal(string line, int op_num)
  {
    // take off the hyphen
    string signal = line.substr(1);
  
    tokenizer< argument_separator > signal_tok(signal);    
    tokenizer< argument_separator >::iterator 
      signal_itr = signal_tok.begin(), end = signal_tok.end();

    if (signal_itr == end)
      throw instruction_parse_error("signal does not have a keyword");
    
    string signal_name = *signal_itr;
    to_lower(signal_name);

    /** function annotations **/
    if (signal_name == "function")
    {
      // increment the token iterator and make sure we have a function name
      if (++signal_itr == end)
        throw instruction_parse_error(FUNCTION_USAGE_ERROR);

      // collect function information
      string function_name = *signal_itr;
      
      // increment, make sure we have a number
      if (++signal_itr == end)
        throw instruction_parse_error(FUNCTION_USAGE_ERROR);
      unsigned int args = 
        parse_nonnegative_integer(*signal_itr, "-function");
      function_map[function_name] = function_info(op_num, args);

      return;
    }

    /** assertion tests **/
    if (signal_name == "assert")
    {
      // get the assertion type
      if (++signal_itr == end)
        throw instruction_parse_error(ASSERT_USAGE_ERROR);
      
      string assertion_name = trim_copy(*signal_itr);
      to_lower(assertion_name);

      if (assertion_name == "error")
      {
        should_throw = true;
      }
      else if (assertion_name == "acc")
      {
        // read the value
        if (++signal_itr == end)
          throw instruction_parse_error(ACC_EQUALS_USAGE_ERROR);
        Value v = Value::parse_value(*signal_itr);

        // add it to the assertion list
        assertions.insert(make_pair(op_num, new acc_equals_test(v)));
        
        return;
      }
      else if (assertion_name == "does_not_reach")
      {
        assertions.insert(make_pair(op_num, new does_not_reach_test()));
      }
      else
        throw instruction_parse_error("unrecognized assert: " + assertion_name);
      
      return;
    }
    
    /** field allocation **/
    if (signal_name == "allocfields")
    {
      if (++signal_itr == end)
        throw instruction_parse_error(ALLOCFIELDS_USAGE_ERROR);

      unsigned int num_fields = parse_nonnegative_integer(*signal_itr, "allocfields");
      alloc_fields(num_fields);
      return;
    }

    /** function initialization **/
    if (signal_name == "init")
    {
      if (++signal_itr == end)
        throw instruction_parse_error(INIT_USAGE_ERROR);

      init_func = *signal_itr;
      return;
    }

    throw instruction_parse_error("unrecognized signal: " + signal_name);
  }

  /** parses a single line containing an instruction, 
      returning the appropriate reference */
  Instruction* Actor::parse_instruction(string instr)
  {
    tokenizer<arg_sep> instr_tokenizer(instr);
    tokenizer<arg_sep>::iterator itr = instr_tokenizer.begin();
    const tokenizer<arg_sep>::iterator end = instr_tokenizer.end();

    string instr_name = *itr;
    to_lower(instr_name);
    ++itr;

#define NO_ARG_INSTRUCTION(name, constructor)          \
    if (instr_name == name)                            \
      return new constructor() 
    
    // instructions without arguments
    NO_ARG_INSTRUCTION("push", push_instr);

    NO_ARG_INSTRUCTION("read", read_instr);

    NO_ARG_INSTRUCTION("print", print_instr);
    NO_ARG_INSTRUCTION("println", println_instr);

    NO_ARG_INSTRUCTION("swap", swap_instr);
    NO_ARG_INSTRUCTION("stop", stop_instr);
    NO_ARG_INSTRUCTION("return", return_instr);

    NO_ARG_INSTRUCTION("cons", cons_instr);
    NO_ARG_INSTRUCTION("car", car_instr);
    NO_ARG_INSTRUCTION("cdr", cdr_instr);
    NO_ARG_INSTRUCTION("nil", nil_instr);

    NO_ARG_INSTRUCTION("isnull", isnull_instr);

    NO_ARG_INSTRUCTION("concat", concat_instr);

    NO_ARG_INSTRUCTION("add", add_instr);
    NO_ARG_INSTRUCTION("sub", sub_instr);
    NO_ARG_INSTRUCTION("mul", mul_instr);
    NO_ARG_INSTRUCTION("div", div_instr);
    NO_ARG_INSTRUCTION("divi", divi_instr);
    NO_ARG_INSTRUCTION("remi", remi_instr);
    NO_ARG_INSTRUCTION("and", and_instr);
    NO_ARG_INSTRUCTION("or", or_instr);
    
    NO_ARG_INSTRUCTION("neg", neg_instr);
    NO_ARG_INSTRUCTION("frac", frac_instr);
    NO_ARG_INSTRUCTION("int", int_instr);
    NO_ARG_INSTRUCTION("not", not_instr);

    NO_ARG_INSTRUCTION("lt", lt_instr);
    NO_ARG_INSTRUCTION("gt", gt_instr);
    NO_ARG_INSTRUCTION("lte", lte_instr);
    NO_ARG_INSTRUCTION("gte", gte_instr);
    NO_ARG_INSTRUCTION("eq", eq_instr);
    NO_ARG_INSTRUCTION("neq", neq_instr);

    NO_ARG_INSTRUCTION("apply", apply_instr);

    /**** optional zero or one argument instructions ****/

#define OPTIONAL_NO_ARG_INSTRUCTION(name, constructor)          \
    if (instr_name == name && (itr == end))                     \
      return new constructor() 
    
    OPTIONAL_NO_ARG_INSTRUCTION("pop", pop_instr);
    OPTIONAL_NO_ARG_INSTRUCTION("const", const_instr);
    OPTIONAL_NO_ARG_INSTRUCTION("getelem", getelem_NP_instr);
    OPTIONAL_NO_ARG_INSTRUCTION("setelem", setelem_NP_instr);

    /**** one argument instructions ****/
    if (itr == end)
      throw instruction_parse_error(instr_name +
                                    " cannot be parsed as an instruction because" +
                                    " it is not a zero-argument instruction.");    
    
    // construct the next argument
    string arg1 = *itr;
    ++itr;

#define INTEGER_ARG_INSTRUCTION(name, constructor)      \
    if (instr_name == name)                             \
      return new constructor(                           \
        parse_nonnegative_integer(arg1, name))

    INTEGER_ARG_INSTRUCTION("pop", pop_instr);
  
    INTEGER_ARG_INSTRUCTION("assign", assign_instr);
    INTEGER_ARG_INSTRUCTION("acc", acc_instr);
    INTEGER_ARG_INSTRUCTION("rev", rev_instr);

    INTEGER_ARG_INSTRUCTION("getelem", getelem_instr);
    INTEGER_ARG_INSTRUCTION("setelem", setelem_instr);

    INTEGER_ARG_INSTRUCTION("getfield", get_field_instr);
    INTEGER_ARG_INSTRUCTION("setfield", set_field_instr);

    if (instr_name == "makeblock")
      return new make_block_instr(
        (Block::Tag)parse_nonnegative_integer(arg1, "makeblock"));
    
    if (instr_name == "makefilledblock")
      return new make_filled_block_instr(
        (Block::Tag)parse_nonnegative_integer(arg1, "makefilledblock"));

    if (instr_name == "const")
      return new const_instr(Value::parse_value(arg1));
    if (instr_name == "pushsf")
      return new push_sf_instr(get_label_address(arg1));
    if (instr_name == "call")
      return new call_instr(arg1, get_label_address(arg1));
    if (instr_name == "jmp")
      return new jmp_instr(get_label_address(arg1));
    if (instr_name == "jz")
      return new jz_instr(get_label_address(arg1));
    if (instr_name == "jnz")
      return new jnz_instr(get_label_address(arg1));
    if (instr_name == "rpc")
      return new rpc_instr(arg1);
    
    /**** optional one or two-arg instructions ****/
    if (instr_name == "dump" && (itr == end))
    {
      try 
      {
        return new stack_dump_instr(Value::parse_value(arg1).get_string());
      }
      catch (runtime_error e)
      {
        throw instruction_parse_error("first argument of dump should be a string");
      }
    }

    /**** two-argument instructions ****/
    if (itr == end)
      throw instruction_parse_error(instr_name + 
                                    string(" cannot be parsed as an instruction because ") +
                                    string("it is not a 1-argument instruction."));

    // read in the second argument
    string arg2 = *itr;

    if (instr_name == "makeblockstatic")
      return new make_block_static_instr(
        (Block::Tag)parse_nonnegative_integer(arg1, "makeblockstatic"),
        parse_nonnegative_integer(arg2, "makeblockstatic"));

    if (instr_name == "dump")
      try
      {
        return new stack_dump_instr(
          Value::parse_value(arg1).get_string(),
          parse_nonnegative_integer(arg2, "dump"));
      }
      catch (runtime_error e)
      {
        throw instruction_parse_error("arguments to dump incorrect, expects string, [num'");
      }

    // if we've reached here, then we haven't implemented
    // a way to parse the instruction
    throw instruction_parse_error(instr_name +
                                  " cannot be parsed as an instruction.");
  }

  /**
   * @param name the name of the instructions
   * @throws instruction_parse_error if the argument is not a nonnegative
   * integer
   */
  unsigned int Actor::parse_nonnegative_integer(string arg, string name)
  {
    istringstream iss(arg);
    int ret;
    iss >> ret;
    if (iss.bad() || ret < 0)
      throw instruction_parse_error(name + " expects nonnegative integer value(s)" +
                                    (", given " + arg));

    return (unsigned int)ret;
  }
}
